/**
 * 中缀表达式求值工具，支持由 + - * / ^ 组合的运算
 */

export class RpnUtil {
    public static precedence: Map<string, number> = new Map<string, number>(
        [
            ['^', 3],
            ['/', 2],
            ['*', 2],
            ['+', 1],
            ['-', 1],
            ['#', 0]
        ]
    );

    public static operation: Map<string, any> = new Map<string, any>(
        [
            ['^', (a, b) => {return Math.pow((+a), (+b))}],
            ['/', (a, b) => {return (+a) / (+b)}],
            ['*', (a, b) => {return (+a) * (+b)}],
            ['+', (a, b) => {return (+a) + (+b)}],
            ['-', (a, b) => {return (+a) - (+b)}],
        ]
    );

    public static spilitExp(exp: string): Array<string>{
        return exp.match(/(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))|(\d+(\.\d+)?)|[^\d\s\t]/g);
    }

    public static isOperator(char: string): boolean {
        return /^[\/\*\-\+\^#]$/.test(char);
    }

    public static isBracket(char: string): boolean {
        return /^[\(\)]$/.test(char);
    }

    public static isNumber(str: string) :boolean {
        return /^\d+(\.\d+)?$/.test(str) || /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/.test(str);
    }

    public static isValidExpression(exp: string): boolean {
        return !/[^\d\s\t\^\.\+\-\*\/\(\)]/.test(exp);
    }

    public static infix2rpn(exp: string): string {
        if (! this.isValidExpression(exp)) {
            return null;
        }

        let arrExp = this.spilitExp(exp).concat('#');
        let opStack = [];
        let rpnStack = [];

        let item,
            op,
            len = arrExp.length;

        for (let i = 0; i < len; i++) {
            item = arrExp[i];
            if (this.isNumber(item)) {
                //是数字直接放入结果栈
                rpnStack.push(item);
            } else if (this.isOperator(item)) {
                //是运算符
                while (opStack.length) {
                    op = opStack[opStack.length - 1];
                    if (op === '(') {
                        //运算符栈栈顶是左括号需要单独处理
                        break;
                    } else if (this.precedence.get(item) > this.precedence.get(op)) {
                        //当前运算符优先级大于运算符栈栈顶优先级
                        break;
                    } else {
                        //当前运算符优先级小于运算符栈栈顶优先级，弹出运算符栈栈顶算符加入结果栈
                        rpnStack.push(opStack.pop());
                    }
                }
                opStack.push(item);
            } else {
                if (item === '(') {
                    opStack.push(item);
                } else {
                    //否则是')'，遍历运算符栈到上次出现'('的地方，将中间的所有运算符加入结果栈
                    while (opStack[opStack.length - 1] !== '(') {
                        rpnStack.push(opStack.pop());
                    }
                    //弹出'('
                    opStack.pop();
                }
            }
        }
        return rpnStack.length ? rpnStack.join(' ') : null;
    }

    public static rpnCalculate(exp: string): number {
        if (!this.isValidExpression(exp)) {
            return null;
        }

        let arrExp = this.spilitExp(exp);
        let item,
            paraml1,
            paraml2,
            calcStack = [],
            len = arrExp.length;

        for (let i = 0; i < len; i++) {
            item = arrExp[i];
            if (this.isNumber(item)) {
                calcStack.push(+item);
            } else {
                paraml2 = calcStack.pop();
                paraml1 = calcStack.pop();
                calcStack.push(this.operation.get(item)(paraml1, paraml2));
            }
        }

        return calcStack.pop();
    }

    public static calculate(exp: string): number {
        return this.rpnCalculate(this.infix2rpn(exp));
    }
}

// Log(Rpn.calculate("(2 ^ ( 5 + 1.2 ))"));